//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
/*
 * Generic xmit layer for the PXA USB client function
 *
 * This code was loosely inspired by the original version which was
 * Copyright (c) Compaq Computer Corporation, 1998-1999
 * Copyright (c) 2001 by Nicolas Pitre
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 02-May-2002
 *   Frank Becker (Intrinsyc) - derived from sa1100 usb_send.c
 *
 * TODO: Add support for DMA.
 *
 */

#ifdef HAVE_CONFIG_H
# include <blob/config.h>
#endif

#if 0
#include <blob/arch.h>
#include <blob/types.h>
#include <blob/init.h>
#endif

#include <linux/errno.h>
#include "pxa_usb.h"
#include "bvd_usb_ctl.h"
#include "types.h"
#include "util.h"
#include "time.h"
#include "bulverde.h"

#undef DEBUG
//#define DEBUG 3
#if DEBUG
static unsigned int usb_debug = DEBUG;
#else
#define usb_debug 0     /* gcc will remove all the debug code for us */
#endif

static char *ep1_buf;
static int   ep1_len;
static int   ep1_remain;
static usb_callback_t ep1_callback;
static int tx_pktsize;

/* device state is changing, async */
void
ep1_state_change_notify(int new_state)
{
}

/* set feature stall executing, async */
void
ep1_stall(void)
{
}

void
ep1_send_packet(void)
{
    int i, bytes, bytes_this_time, bytes_left;
    __u8 *buf = ep1_buf + ep1_len - ep1_remain;
    int out_size = tx_pktsize;
    __u32 aligned_buf[64];
    __u8 *data;

/* Nam9, 2004. 9. 3 */
    data = (char *)aligned_buf;

    if (usb_debug)
        printk( "%s: enter \n", __FUNCTION__);

    if (out_size > ep1_remain) {
        out_size = ep1_remain;
    }

    bytes_left = out_size;

    do {
        bytes_this_time = (bytes_left < sizeof(aligned_buf))
                    ? bytes_left : sizeof(aligned_buf);

        data = (char *)aligned_buf;
        memcpy(data, buf, bytes_this_time);

        bytes = bytes_this_time & (~0x3);
        for (i = 0; i < bytes; i += 4) {
            UDCDRA = *((__u32*)data);
            data += 4;
        }

        bytes = bytes_this_time & 0x3;
        for (i = 0; i < bytes; i++)
            *((volatile u8*)&UDCDRA) = *data++;

        buf += bytes_this_time;
        bytes_left -= bytes_this_time;
    } while (bytes_left);

    /* UDCCS1 = UDCCS_BI_TPC; */
    UDCCSRA = UDCCSR_PC;

    if (out_size < tx_pktsize) {
        /* short packet */
        UDCCSRA = UDCCSR_SP;
    }
    ep1_remain -= out_size;

    /* alek added 2004-12-30 */
    while (!(UDCISR0 & UDCISR0_IRA)) {
        /* printk("."); */
    }
    ;

    if (usb_debug)
        printk("%s: exit, ep1_remain=%d bytes\n", __FUNCTION__, ep1_remain);
}

void
ep1_start(void)
{
    if (!ep1_len)
        return;

    UDCICR0 = 0x3f;

    ep1_send_packet();
}

void
ep1_done(int flag)
{
    int size = ep1_len - ep1_remain;
    if (ep1_len) {
        ep1_len = 0;
        if (ep1_callback) {
            ep1_callback(flag, size);
        }
    }
}

int
ep1_init(int chn)
{
    /*
    int i;
    desc_t * pdesc = pxa_usb_get_descriptor_ptr();
    for (i = 0; i < pdesc->ep_num; i++) {
        if(BULK_IN1 == (0xF & pdesc->ep[i].bEndpointAddress)) {
            tx_pktsize = __le16_to_cpu(pdesc->ep[i].wMaxPacketSize);
        }
    }
    */

    /* FIXME */
    tx_pktsize = 64;
    ep1_done(-EAGAIN);
    return 0;
}

void
ep1_reset(void)
{
    /*
    int i;
    desc_t * pdesc = pxa_usb_get_descriptor_ptr(); 
    for (i = 0; i < pdesc->ep_num; i++) {
        if(BULK_IN1 == (0xF & pdesc->ep[i].bEndpointAddress)) {
            tx_pktsize = __le16_to_cpu( pdesc->ep[i].wMaxPacketSize);
        }
    }
    */

    /* FIXME */
    tx_pktsize = 64;

    ep1_done(-EINTR);
}

void
ep1_int_hndlr(int usir0)
{
    int status = UDCCSRA;

    if (ep1_remain != 0) {
        /* more data to go */
        ep1_start();
    }
    else {
        if (status & UDCCSR_PC) {
            UDCCSRA = UDCCSR_PC;
        }
        ep1_done(0);
    }
}

int
ep1_send(char *buf, int len, usb_callback_t callback)
{
    int i=0;
    int flags;

    if (usb_debug)
        printk( "pxa_usb_send: "
        "data len=%d state=%d blen=%d\n",
        len, usbd_info.state, ep1_len);

    if (usbd_info.state != USB_STATE_CONFIGURED)
        return -ENODEV;

    if (ep1_len) {
        return -EBUSY;
    }

    ep1_buf = buf;
    ep1_len = len;
    ep1_callback = callback;
    ep1_remain = len;
    ep1_start();

    return 0;
}

int
ep1_xmitter_avail(void)
{
    if (usbd_info.state != USB_STATE_CONFIGURED)
        return -ENODEV;
    if (ep1_len)
        return -EBUSY;
    return 0;
}
